import 'dart:io';

import 'package:capsule/enums/message_type.dart';
import 'package:capsule/models/config_file_type.dart';
import 'package:capsule/models/progress_info.dart';
import 'package:capsule/models/title_value_pair.dart';
import 'package:capsule/utils/callbacks.dart';
import 'package:capsule/widgets/dialog/input_dialog.dart';
import 'package:capsule/widgets/form/form_input_with_icon.dart';
import 'package:capsule/widgets/progress_prompt_bar.dart';
import 'package:file_picker/file_picker.dart';

// import 'package:enhanced_url_launcher/enhanced_url_launcher_string.dart';
import 'package:flutter/material.dart';
import 'package:open_app_file/open_app_file.dart';
import 'package:split_view/split_view.dart';

import '../app_options/app_theme_data.dart';
import '../app_options/colors.dart';

import '../models/order_field_info.dart';
import '../utils/global_variables.dart';
import '../models/file_info.dart';
import '../utils/common_utils.dart';

import 'package:sqlite3/sqlite3.dart' as sqlite;

import '../widgets/count_panel.dart';
import '../widgets/dialog/file_info_editor_dialog.dart';
import '../widgets/tag_board.dart';

class CapsuleHomePage extends StatefulWidget {
  const CapsuleHomePage({super.key, required this.title});

  final String title;

  @override
  State<CapsuleHomePage> createState() => _CapsuleHomePageState();
}

class _CapsuleHomePageState extends State<CapsuleHomePage> {
  final List<FileInfo> _fileInfos = [];
  Map<String, bool> _filterFileTypeMap = {};
  final ValueNotifier<ProgressInfo> _listingFileProgressNotifier =
      ValueNotifier(const ProgressInfo(prompt: "就绪"));
  final TextEditingController _keywordController = TextEditingController();
  final TextEditingController _newTagController = TextEditingController();
  String? _currentFolder;
  final List<String> _tags = [];
  final Map<String, List<String>> _fileAndTagsMap = {};
  final Map<String, String> _fileExistsMap = {};
  final List<String> _filterTags = [];
  final String _sqliteCreateCapsuleTable =
      "CREATE TABLE capsule(id INTEGER NOT NULL PRIMARY KEY,"
      "name TEXT NOT NULL,path TEXT,"
      "category TEXT,shelf TEXT,"
      "size INTEGER,sizeString TEXT,"
      "ext TEXT,fileType TEXT,lastAccessed INTEGER,"
      "lastModified INTEGER,importTime INTEGER,"
      "shortPinyin TEXT,summary TEXT)";
  final String _sqliteCreateCapsuleIndex =
      "CREATE INDEX capsule_keywords on capsule(name,path,shelf,ext,category,fileType,shortPinyin,summary)";

  final String _sqliteCreateTagTable =
      "CREATE TABLE tag(id INTEGER NOT NULL PRIMARY KEY,tag TEXT NOT NULL)";
  final String _sqliteCreateTagIndex = "CREATE INDEX tag_keywords on tag(tag)";

  final String _sqliteCreateCapsuleTagTable =
      "CREATE TABLE capsule_tag(capsule_name TEXT NOT NULL,tag TEXT NOT NULL)";
  final String _sqliteCreateCapsuleTagIndex =
      "CREATE INDEX capsule_tag_keywords on capsule_tag(capsule_name,tag)";

  final String _sqliteCreateCapsuleFileTable =
      "CREATE TABLE capsule_file(capsule_name TEXT NOT NULL,file_name TEXT NOT NULL)";

  final String _sqliteCreateCapsuleFileIndex =
      "CREATE INDEX capsule_file_keywords on capsule_file(capsule_name,file_name)";

  bool _isAddingToDb = false;
  double _addingToDBProgressValue = 0;
  String _addingProgressLabel = "";
  bool _cancelAddingToDB = false;
  final SplitViewController _splitViewController =
      SplitViewController(weights: [0.2], limits: [WeightLimit(max: 0.2)]);
  late final String _fileDBPath;
  final Map<int, FileInfo> _selectionInQueryResult = {};
  final Map<String, FileInfo> _selectionInFileList = {};
  final List<TitleValuePair> _countShelfResult = [];
  final List<TitleValuePair> _countExtResult = [];
  final List<TitleValuePair> _countFileTypeResult = [];
  String? _currentKeyword;
  OrderFieldInfo? _orderFieldInfo;
  final List<OrderFieldInfo> _orderFields = const [
    OrderFieldInfo(name: "nameASC", title: "按名称升序", orderFields: "shortPinyin"),
    OrderFieldInfo(
        name: "nameDESC",
        title: "按名称降序",
        orderFields: "shortPinyin",
        sort: "DESC"),
    OrderFieldInfo(
        title: "按时间升序", name: "lastModifiedASC", orderFields: "lastModified"),
    OrderFieldInfo(
        title: "按时间降序",
        name: "lastModifiedDESC",
        sort: "DESC",
        orderFields: "lastModified")
  ];
  @override
  void initState() {
    super.initState();
    _orderFieldInfo = _orderFields[0];
    String appPath = Directory.current.path;
    _fileDBPath = "$appPath\\file_db.db";
    _createDB().then((existed) {
      if (existed == true) {
        _getTagsFromDb();
        _query().then((value) {
          _getCapsuleTagsFromDB();
          for (final fileInfo in _fileInfos) {
            _fileExistsMap[fileInfo.title] = fileInfo.fullPath;
          }
        });
      }
    });
  }

  @override
  void dispose() {
    _listingFileProgressNotifier.dispose();
    _keywordController.dispose();
    _newTagController.dispose();
    super.dispose();
  }

  void _showAddFileToDBDlg() {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: InputDialog(
              prompt: "请输入位置(假如你有多块硬盘...)：",
              onCancel: () {
                Navigator.of(context).pop();
              },
              onOk: (value) {
                Navigator.of(context).pop();
                Future.delayed(const Duration(milliseconds: 200), () {
                  _addFilesToDB(value);
                });
              },
            ),
          );
        });
  }

  void _updateFileInfo(
      FileInfo oldFileInfo, FileInfo newFileInfo, sqlite.Database db) {
    final updateCapsuleCommand =
        db.prepare("UPDATE capsule set name=?,path=?,summary=? where id=?");
    updateCapsuleCommand.execute([
      newFileInfo.title,
      newFileInfo.fullPath,
      newFileInfo.summary,
      newFileInfo.idInDB
    ]);
    updateCapsuleCommand.dispose();

    if (newFileInfo.title != oldFileInfo.title) {
      final updateCapsuleTagCommand = db.prepare(
          "UPDATE capsule_tag set capsule_name=? where capsule_name=?");
      updateCapsuleTagCommand.execute([newFileInfo.title, oldFileInfo.title]);
      updateCapsuleTagCommand.dispose();
    }
  }

  void _showFileInfoEditDlg(FileInfo fileInfo) {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: FileInfoEditorDialog(
              fileInfo: fileInfo,
              onCancel: () {
                Navigator.of(context).pop();
              },
              onOk: (value) {
                Navigator.of(context).pop();
                if (value == null) return;
                sqlite.Database? db;
                try {
                  db = sqlite.sqlite3.open(_fileDBPath);
                  bool? isFileList = _isFileList();

                  if (isFileList == false) {
                    _updateFileInfo(fileInfo, value, db);
                  } else if (isFileList == true) {
                    sqlite.ResultSet rs = db.select(
                        "SELECT * FROM capsule WHERE name='${fileInfo.title}'");
                    if (rs.isEmpty) {
                      showDialog(
                          context: context,
                          builder: (context) {
                            return Dialog(
                              child: InputDialog(
                                prompt: "该记录不在数据库中，将新建，请输入位置(假如你有多块硬盘...)：",
                                onCancel: () {
                                  Navigator.of(context).pop();
                                },
                                onOk: (value) {
                                  Navigator.of(context).pop();
                                  Future.delayed(
                                      const Duration(milliseconds: 200), () {
                                    String insertSQL =
                                        "INSERT INTO capsule(name,path,shelf,size,sizeString,ext,category,fileType,lastAccessed,lastModified,importTime,shortPinyin) VALUES";

                                    StringBuffer sbInsertSQL =
                                        StringBuffer(insertSQL);
                                    sbInsertSQL.write(
                                        "('${fileInfo.title}','${fileInfo.fullPath}','$value',"
                                        "${fileInfo.size},'${fileInfo.sizeString}','${fileInfo.ext}',"
                                        "'${fileInfo.category}','${fileInfo.fileTypeName}',"
                                        "${fileInfo.lastAccessed},${fileInfo.lastModified},"
                                        "${DateTime.now().millisecondsSinceEpoch},'${fileInfo.shortPinyin}')");
                                    db!.execute(sbInsertSQL.toString());
                                  });
                                },
                              ),
                            );
                          });
                    } else {
                      _updateFileInfo(fileInfo, value, db);
                    }
                  }
                  _fileInfos[_fileInfos.indexOf(fileInfo)] = value;
                  if (fileInfo.title != value.title) {
                    if (_fileAndTagsMap.containsKey(fileInfo.title)) {
                      _fileAndTagsMap[value.title] =
                          _fileAndTagsMap[fileInfo.title]!;
                      _fileAndTagsMap.remove(fileInfo.title);
                    }
                  }

                  setState(() {});
                } catch (ex) {
                  if (mounted) {
                    showSnackBar(context, "修改失败：${ex.toString()}");
                  }
                } finally {
                  db?.dispose();
                }
              },
            ),
          );
        });
  }

  Future<bool> _createDB() async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == true) {
      return true;
    }
    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);

    if (existed == false) {
      db.execute(_sqliteCreateCapsuleTable);
      db.execute(_sqliteCreateCapsuleIndex);
      db.execute(_sqliteCreateTagTable);
      db.execute(_sqliteCreateTagIndex);
      db.execute(_sqliteCreateCapsuleTagTable);
      db.execute(_sqliteCreateCapsuleTagIndex);
      db.execute(_sqliteCreateCapsuleFileTable);
      db.execute(_sqliteCreateCapsuleFileIndex);
    }

    db.dispose();

    return false;
  }

  Future<void> _removeRepeatedCapsules() async {
    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);
    String sql = "SELECT * FROM capsule ORDER BY id";
    sqlite.ResultSet resultSet = db.select(sql);

    Map<String, List<int>> nameAndIdsMap = {};

    for (final sqlite.Row row in resultSet) {
      String? name = row["name"];
      if (name != null) {
        if (nameAndIdsMap.containsKey(name) == false) {
          nameAndIdsMap[name] = [];
        }
        nameAndIdsMap[name]!.add(row["id"] as int);
      }
    }

    List<int> idsRemoved = [];
    for (MapEntry<String, List<int>> entry in nameAndIdsMap.entries) {
      if (entry.value.length > 1) {
        idsRemoved.addAll(entry.value.sublist(0, entry.value.length - 1));
      }
    }
    if (idsRemoved.isNotEmpty) {
      String deleteSQL =
          "DELETE FROM capsule WHERE id in (${idsRemoved.join(",")})";
      db.execute(deleteSQL);
    }

    db.dispose();

    showSnackBar(context, "去重成功！", messageType: MessageType.success);
  }

  Future<void> _addFilesToDB(String shelf) async {
    try {
//Directory appDir = await getApplicationDocumentsDirectory();
      _cancelAddingToDB = false;

      // db.execute(sql)
      // final capsuleInsert = db.prepare(
      //     "INSERT INTO capsule(name,path,shelf,size,sizeString) VALUES(?,?,?,?,?)");
      int counter = 0;
      int total = _fileInfos.length;
      setState(() {
        _isAddingToDb = true;
      });

      String insertSQL =
          "INSERT INTO capsule(name,path,shelf,size,sizeString,ext,category,fileType,lastAccessed,lastModified,importTime,shortPinyin) VALUES";

      StringBuffer sbInsertSQL = StringBuffer(insertSQL);

      List<String> updateSQLs = [];
      await Future.delayed(const Duration(milliseconds: 500));
      for (final fileInfo in _fileInfos) {
        String? existedPath = _fileExistsMap[fileInfo.title];
        if (existedPath == null) {
          if (counter > 0) {
            sbInsertSQL.write(",");
          }
          String title = fileInfo.title;
          if (title.contains("'")) {
            title = title.replaceAll("'", "’");
          }
          String fullPath = fileInfo.fullPath;
          if (fullPath.contains("'")) {
            fullPath = fullPath.replaceAll("'", "’");
          }
          String shelfTemp = shelf;
          if (shelfTemp.contains("'")) {
            shelfTemp = shelfTemp.replaceAll("'", "’");
          }
          sbInsertSQL.write("('$title','$fullPath','$shelfTemp',"
              "${fileInfo.size},'${fileInfo.sizeString}','${fileInfo.ext}',"
              "'${fileInfo.category}','${fileInfo.fileTypeName}',"
              "${fileInfo.lastAccessed},${fileInfo.lastModified},"
              "${DateTime.now().millisecondsSinceEpoch},'${fileInfo.shortPinyin}')");
          counter++;
          setState(() {
            _addingToDBProgressValue = counter / total;
            _addingProgressLabel = "已入库$counter/$total";
          });
          if (_cancelAddingToDB == true) break;
        } else {
          if (existedPath.toLowerCase() != fileInfo.fullPath.toLowerCase()) {
            updateSQLs.add(
                "UPDATE capsule SET path='${fileInfo.fullPath}' WHERE name='${fileInfo.title}'");
          }
        }
      }
      if (counter > 0 || updateSQLs.isNotEmpty) {
        final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);
        if (counter > 0) {
          db.execute(sbInsertSQL.toString());
        }
        if (updateSQLs.isNotEmpty) {
          for (final updateSQL in updateSQLs) {
            db.execute(updateSQL);
          }
        }

        db.dispose();
      }

      await Future.delayed(const Duration(milliseconds: 500));
      setState(() {
        _isAddingToDb = false;
        _addingProgressLabel = "入库完成";
      });
      // capsuleInsert.dispose();
    } catch (ex) {
      if (mounted) {
        showSnackBar(context, "导入出现错误：${ex.toString()}",
            messageType: MessageType.error, durationSeconds: 20);
      }
    }
  }

  Future<void> _setShortPinyinInDB() async {
    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);

    for (final fileInfo in _fileInfos) {
      String updateSQL =
          "UPDATE capsule SET shortPinyin='${fileInfo.shortPinyin}' WHERE name='${fileInfo.title}'";
      db.execute(updateSQL);
    }

    db.dispose();
    showSnackBar(context, "生成名称简拼完成", messageType: MessageType.success);
  }

  Future<void> _getCapsuleTagsFromDB(
      {sqlite.Database? db, bool refresh = true}) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      return;
    }
    _fileAndTagsMap.clear();
    final sqlite.Database actualDB = db ?? sqlite.sqlite3.open(_fileDBPath);

    const String selectAllSQL = "SELECT * FROM capsule_tag";
    sqlite.ResultSet resultSet;

    resultSet = actualDB.select(selectAllSQL);

    for (final sqlite.Row row in resultSet) {
      String capsuleName = row["capsule_name"];
      String tag = row["tag"];
      if (_fileAndTagsMap.containsKey(capsuleName) == false) {
        _fileAndTagsMap[capsuleName] = [];
      }
      _fileAndTagsMap[capsuleName]!.add(tag);
    }
    if (refresh) {
      if (mounted) setState(() {});
    }

    if (db == null) {
      actualDB.dispose();
    }
  }

  List<String> _getExtensions() {
    List<String> extensions = [];
    if (appConfig.fileTypes != null) {
      bool all = _filterFileTypeMap.isEmpty;
      if (_filterFileTypeMap.isNotEmpty) {
        for (int i = 0; i < _filterFileTypeMap.entries.length; i++) {
          if ((i + 1) < _filterFileTypeMap.entries.length) {
            if (_filterFileTypeMap.entries.elementAt(i).value !=
                _filterFileTypeMap.entries.elementAt(i + 1).value) {
              all = false;
            }
          }
        }
      }
      if (all) {
        for (final ft in appConfig.fileTypes!) {
          if (ft.extensions != null) {
            extensions.addAll(ft.extensions!);
          }
        }
      } else {
        for (final ft in appConfig.fileTypes!) {
          if (ft.extensions != null) {
            if (_filterFileTypeMap[ft.name] == true) {
              extensions.addAll(ft.extensions!);
            }
          }
        }
      }
    }
    return extensions;
  }

  void _listFiles() async {
    List<String> allowedExtensions = _getExtensions();
    // String? folder =
    //     await showFolderSelector(context, allowedExtensions: allowedExtensions);
    String? folder = await FilePicker.platform.getDirectoryPath();
    if (folder == null) return;
    _currentFolder = folder;
    if (mounted) {
      _fileInfos.clear();
      _clearAllSelection(false);
      _listingFileProgressNotifier.value = _listingFileProgressNotifier.value
          .copyWith(prompt: folder, finished: false);
      File fileEntity = File(folder);
      if (fileEntity.statSync().type == FileSystemEntityType.directory) {
        final Directory selectedDir = Directory(folder);
        final stream = selectedDir.list(recursive: true, followLinks: false);
        stream.listen(
          (event) {
            if (event.statSync().type == FileSystemEntityType.file) {
              String filePath = event.path;
              List<String> nameParts = filePath.split(".");
              String? ext = nameParts.length >= 2
                  ? nameParts[nameParts.length - 1]
                  : null;
              if (allowedExtensions.contains(".$ext") == true) {
                final File file = File(filePath);
                FileInfo fileInfo = FileInfo(filePath,
                    size: file.lengthSync(),
                    lastAccessed:
                        (file.lastAccessedSync()).millisecondsSinceEpoch,
                    lastModified:
                        (file.lastModifiedSync()).millisecondsSinceEpoch);

                _fileInfos.add(fileInfo);

                _listingFileProgressNotifier.value =
                    _listingFileProgressNotifier.value
                        .copyWith(prompt: "$folder：${_fileInfos.length}");
              }
            }
          },
          onDone: () {
            _listingFileProgressNotifier.value =
                _listingFileProgressNotifier.value.copyWith(finished: true);
            setState(() {});
          },
        );
      } else {
        final File file = File(folder);
        setState(() {
          _fileInfos.add(FileInfo(folder, size: file.lengthSync()));
        });
      }
    }
  }

  void _clearItemInList() {
    _clearAllSelection(false);
    setState(() {
      _fileInfos.clear();
    });

    _listingFileProgressNotifier.value =
        _listingFileProgressNotifier.value.copyWith(prompt: "就绪");
  }

  void _onlyRemoveSelectedItems() {
    bool? isFileList = _isFileList();
    if (isFileList == true) {
      if (_selectionInFileList.isNotEmpty) {
        for (final entry in _selectionInFileList.entries) {
          _fileInfos.remove(entry.value);
        }
        _selectionInFileList.clear();
      }
    } else if (isFileList == false) {
      if (_selectionInQueryResult.isNotEmpty) {
        for (final entry in _selectionInQueryResult.entries) {
          _fileInfos.remove(entry.value);
        }
        _selectionInQueryResult.clear();
      }
    }
    setState(() {});
  }

  void _deleteFileInfo(FileInfo fileInfo) async {
    bool? isFileList = _isFileList();
    if (isFileList == null) return;
    showDialog(
        context: context,
        builder: (context) {
          String dataTitle = isFileList == true ? "文件" : "记录";
          return AlertDialog(
            title: Text("删除$dataTitle"),
            content: Text("确定要删除$dataTitle【${fileInfo.title}】么？删除后将不能恢复."),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text("否")),
              ElevatedButton(
                  onPressed: () async {
                    Navigator.of(context).pop();
                    if (isFileList == true) {
                      File file = File(fileInfo.fullPath);
                      if (await file.exists() == true) {
                        await file.delete();
                      }

                      if (mounted) {
                        _selectionInFileList.remove(fileInfo.title);
                        showSnackBar(context, "删除成功",
                            messageType: MessageType.success);
                      }
                    } else if (isFileList == false) {
                      File dbFile = File(_fileDBPath);
                      bool existed = await dbFile.exists();
                      if (existed == false) {
                        if (mounted) {
                          showSnackBar(context, "尚未建库，无法删除");
                        }
                      }
                      final sqlite.Database db =
                          sqlite.sqlite3.open(_fileDBPath);
                      db.execute(
                          "DELETE FROM capsule WHERE id=${fileInfo.idInDB}");

                      db.dispose();

                      File file = File(fileInfo.fullPath);
                      if (await file.exists() == true) {
                        await file.delete();
                      }
                      _selectionInQueryResult.remove(fileInfo.idInDB);
                    }
                    _fileInfos.remove(fileInfo);
                    setState(() {});
                  },
                  child: const Text("是")),
            ],
          );
        });
  }

  void _deleteSelection() async {
    bool? isFileList = _isFileList();
    if (isFileList == null) return;
    if (_validateSelection() == false) return;
    showDialog(
        context: context,
        builder: (context) {
          String dataTitle = isFileList == true ? "文件" : "记录";
          return AlertDialog(
            title: Text("删除选中$dataTitle"),
            content: Text("确定要删除这些$dataTitle么？删除后将不能恢复."),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text("否")),
              ElevatedButton(
                  onPressed: () async {
                    Navigator.of(context).pop();
                    if (isFileList == true) {
                      for (final entry in _selectionInFileList.entries) {
                        File file = File(entry.value.fullPath);
                        if (await file.exists() == true) {
                          await file.delete();
                        }
                      }
                      if (mounted) {
                        _removeSelectionInFlieList();

                        showSnackBar(context, "删除成功",
                            messageType: MessageType.success);
                      }
                    } else if (isFileList == false) {
                      File dbFile = File(_fileDBPath);
                      bool existed = await dbFile.exists();
                      if (existed == false) {
                        if (mounted) showSnackBar(context, "尚未建库，无法删除");
                      }
                      final sqlite.Database db =
                          sqlite.sqlite3.open(_fileDBPath);
                      db.execute(
                          "DELETE FROM capsule ${_generateWhereClauseBySelection()}");

                      db.dispose();

                      for (final entry in _selectionInQueryResult.entries) {
                        File file = File(entry.value.fullPath);
                        if (await file.exists() == true) {
                          await file.delete();
                        }
                      }
                      _removeSelectionInQuery();
                    }
                  },
                  child: const Text("是")),
            ],
          );
        });
  }

  Future<void> _doDeleteTag(String s, bool deleteRelation) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      if (mounted) showSnackBar(context, "尚未建库，无法删除");
    }
    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);
    db.execute("DELETE FROM tag WHERE tag='$s'");
    _tags.remove(s);
    if (deleteRelation == true) {
      db.execute("DELETE FROM capsule_tag WHERE tag='$s'");
      for (final MapEntry<String, List<String>> entry
          in _fileAndTagsMap.entries) {
        entry.value.remove(s);
      }
    }

    db.dispose();

    setState(() {});
  }

  Future<void> _doEditTag(String oldTag, String newTag) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      if (mounted) showSnackBar(context, "尚未建库，无法修改");
    }
    if (_tags.contains(newTag)) {
      if (mounted) showSnackBar(context, "标签已经存在！");
      return;
    }
    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);
    db.execute("UPDATE tag SET tag='$newTag' WHERE tag='$oldTag'");
    if (_tags.contains(oldTag)) {
      _tags[_tags.indexOf(oldTag)] = newTag;
    }

    db.execute("UPDATE capsule_tag SET tag='$newTag' WHERE tag='$oldTag'");
    for (final MapEntry<String, List<String>> entry
        in _fileAndTagsMap.entries) {
      if (entry.value.contains(oldTag)) {
        entry.value[entry.value.indexOf(oldTag)] = newTag;
      }
    }

    db.dispose();

    setState(() {});
  }

  void _editTag(String oldTag) {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: InputDialog(
              prompt: "将标签【$oldTag】修改为：",
              onCancel: () {
                Navigator.of(context).pop();
              },
              onOk: (newTag) {
                Navigator.of(context).pop();
                Future.delayed(const Duration(milliseconds: 200), () {
                  _doEditTag(oldTag, newTag);
                });
              },
            ),
          );
        });
  }

  void _deleteTag(String s) async {
    showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: const Text("删除标签"),
            content: Text("确定要删除标签【$s】么？"),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text("否")),
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                    _doDeleteTag(s, false);
                  },
                  child: const Text("仅删除标签")),
              ElevatedButton(
                  onPressed: () async {
                    Navigator.of(context).pop();
                    _doDeleteTag(s, true);
                  },
                  child: const Text("删除标签及其关联信息")),
            ],
          );
        });
  }

  bool _validateSelection() {
    if (_isSelectionEmpty()) {
      showSnackBar(context, "请先选择${_isFileList() == true ? "文件" : "记录"}。",
          messageType: MessageType.error);
      return false;
    } else {
      return true;
    }
  }

  bool _isSelectionEmpty() {
    return (_selectionInFileList.isEmpty && _selectionInQueryResult.isEmpty);
  }

  void _clearAllSelection(bool withRefresh) {
    _selectionInQueryResult.clear();
    _selectionInFileList.clear();
    if (withRefresh) {
      setState(() {});
    }
  }

  void _removeSelectionInQuery() {
    setState(() {
      for (final entry in _selectionInQueryResult.entries) {
        _fileInfos.remove(entry.value);
      }
      _selectionInQueryResult.clear();
      _updateQueryStatus();
    });
  }

  void _removeSelectionInFlieList() {
    setState(() {
      for (final entry in _selectionInFileList.entries) {
        _fileInfos.remove(entry.value);
      }
      _selectionInFileList.clear();
      _updateFileListStatus();
    });
  }

  void _clearCurrentDataset() async {
    bool? isFileList = _isFileList();
    if (isFileList == null) return;
    showDialog(
        context: context,
        builder: (context) {
          String dataTitle = isFileList == true ? "文件" : "记录";
          return AlertDialog(
            title: Text("清除当前所有$dataTitle，只清理记录，不删除源文件。"),
            content: Text("确定要删除这些$dataTitle么？删除后将不能恢复。"),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text("否")),
              ElevatedButton(
                  onPressed: () async {
                    Navigator.of(context).pop();
                    if (isFileList == true) {
                    } else if (isFileList == false) {
                      await _clearRecords(_fileInfos);

                      _clearItemInList();
                    }
                  },
                  child: const Text("是")),
            ],
          );
        });
  }

  void _selectAll() {
    bool? isFileList = _isFileList();
    _selectionInFileList.clear();
    _selectionInQueryResult.clear();
    if (isFileList == true) {
      for (final fileInfo in _fileInfos) {
        _selectionInFileList[fileInfo.fullPath] = fileInfo;
      }
    } else {
      for (final fileInfo in _fileInfos) {
        _selectionInQueryResult[fileInfo.idInDB!] = fileInfo;
      }
    }
    setState(() {});
  }

  Future<void> _clearSelectedRecords() async {
    bool? isFileList = _isFileList();
    if (isFileList == null) return;

    if (_selectionInQueryResult.isEmpty) {
      showSnackBar(context, "未选中记录！", messageType: MessageType.error);
      return;
    }
    showDialog(
        context: context,
        builder: (context) {
          String dataTitle = isFileList == true ? "文件" : "记录";
          return AlertDialog(
            title: Text("清除选中的$dataTitle，只清理记录，不删除源文件。"),
            content: Text("确定要删除这些$dataTitle么？删除后将不能恢复。"),
            actions: [
              ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: const Text("否")),
              ElevatedButton(
                  onPressed: () async {
                    Navigator.of(context).pop();
                    if (isFileList == true) {
                    } else if (isFileList == false) {
                      List<FileInfo> fileInfos = [];
                      for (final entry in _selectionInQueryResult.entries) {
                        fileInfos.add(entry.value);
                      }
                      await _clearRecords(fileInfos);
                      for (final fileInfo in fileInfos) {
                        _fileInfos.remove(fileInfo);
                      }
                      _selectionInQueryResult.clear();
                      if (mounted) {
                        setState(() {});
                        _updateQueryStatus();
                      }
                    }
                  },
                  child: const Text("是")),
            ],
          );
        });
  }

  Future<void> _clearRecords(List<FileInfo> fileInfos) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      if (mounted) showSnackBar(context, "尚未建库，无法删除");
      return;
    }
    try {
      final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);

      db.execute(_getDeleteCapsuleSQL(fileInfos));

      db.dispose();
    } catch (e) {
      if (mounted) showSnackBar(context, e.toString());
    }
  }

  String _getDeleteCapsuleSQL(List<FileInfo> fileInfos) {
    String deleteSQL = "DELETE FROM capsule WHERE id IN (";
    // if (_filterTags.isNotEmpty) {
    //   deleteSQL = "$deleteSQL $_capsuleLeftJoinCapsuleTag";
    // }
    return "$deleteSQL${_joinIdsInFileInfoList(fileInfos)})";
  }

  String _joinIdsInFileInfoList(List<FileInfo> fileInfos) {
    List<int> ids = [];
    for (final fileInfo in fileInfos) {
      ids.add(fileInfo.idInDB!);
    }
    return ids.join(",");
  }

  bool? _isFileList() {
    if (_fileInfos.isNotEmpty) {
      return _fileInfos[0].idInDB == null;
    }
    return null;
  }

  String _generateWhereClauseByKeyword() {
    String? keyword = _currentKeyword;
    String clause = "";
    if (keyword != null && keyword.isNotEmpty == true) {
      clause =
          "(c.name LIKE '%$keyword%' OR c.path LIKE '%$keyword%' OR c.shelf LIKE '%$keyword%' OR c.summary LIKE '%$keyword%')";
    }
    if (_filterTags.isNotEmpty) {
      StringBuffer sbTagClause = StringBuffer("(");
      int cnt = 0;
      for (final String tag in _filterTags) {
        if (cnt > 0) {
          sbTagClause.write(" AND ");
        }
        sbTagClause.write(
            "'$tag' IN (SELECT ct.tag FROM capsule_tag ct WHERE ct.capsule_name=c.name)");
        cnt++;
      }
      sbTagClause.write(")");

      if (clause.isNotEmpty) {
        clause = "$clause AND ${sbTagClause.toString()}";
      } else {
        clause = sbTagClause.toString();
      }
    }
    if (clause.isNotEmpty) {
      return "WHERE $clause";
    } else {
      return clause;
    }
  }

  String _generateWhereClauseBySelection() {
    List<int> idsSelected = [];
    for (final entry in _selectionInQueryResult.entries) {
      idsSelected.add(entry.key);
    }

    return "WHERE id in (${idsSelected.join(",")})";
  }

  Future<void> _query({sqlite.Database? db, bool refresh = true}) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      if (mounted) showSnackBar(context, "尚未建库，无法查询");
      return;
    }

    _currentKeyword = _keywordController.text;
    _listingFileProgressNotifier.value = _listingFileProgressNotifier.value
        .copyWith(finished: false, prompt: "正在查询");
    await Future.delayed(const Duration(milliseconds: 200));
    _fileInfos.clear();
    _clearAllSelection(false);
    final sqlite.Database actualDB = db ?? sqlite.sqlite3.open(_fileDBPath);
    String sql = _getQueryCapsuleSQL();
    sqlite.ResultSet resultSet = actualDB.select(sql);

    for (final sqlite.Row row in resultSet) {
      _fileInfos.add(FileInfo(row["path"],
          size: row["size"],
          idInDB: row["id"] as int,
          fileTypeName: row["fileType"],
          shortPinyin: row["shortPinyin"],
          summary: row["summary"],
          shelf: row["shelf"],
          lastAccessed:
              row["lastAccessed"] != null ? row["lastAccessed"] as int : null,
          lastModified:
              row["lastModified"] != null ? row["lastModified"] as int : null));
    }

    _countCapsule(db: actualDB, refresh: false);
    if (refresh) {
      if (mounted) setState(() {});
    }
    if (db == null) actualDB.dispose();

    _updateQueryStatus();
  }

  Future<void> _countCapsule({sqlite.Database? db, bool refresh = true}) async {
    String sqlCountShelf =
        "SELECT COUNT(c.id) as cnt, c.shelf FROM capsule c ${_generateWhereClauseByKeyword()} GROUP BY shelf";
    String sqlCountExt =
        "SELECT COUNT(c.id) as cnt, c.ext FROM capsule c ${_generateWhereClauseByKeyword()} GROUP BY ext";
    String sqlCountFileType =
        "SELECT COUNT(c.id) as cnt, c.fileType FROM capsule c ${_generateWhereClauseByKeyword()} GROUP BY fileType";

    sqlite.Database? actualDB = db;
    if (db == null) {
      File dbFile = File(_fileDBPath);
      bool existed = await dbFile.exists();
      if (existed == false) {
        if (mounted) showSnackBar(context, "尚未建库，无法查询");
        return;
      }
      actualDB = sqlite.sqlite3.open(_fileDBPath);
    }

    sqlite.ResultSet sqlCountShelfResultSet = actualDB!.select(sqlCountShelf);
    sqlite.ResultSet sqlCountExtResultSet = actualDB.select(sqlCountExt);
    sqlite.ResultSet sqlCountFileTypeResultSet =
        actualDB.select(sqlCountFileType);

    _countShelfResult.clear();
    _countExtResult.clear();
    _countFileTypeResult.clear();

    int cnt = 0;
    for (final sqlite.Row row in sqlCountShelfResultSet) {
      int colorIndex = colorList.length - 1 - cnt;
      if (colorIndex == -1) {
        colorIndex = colorList.length - 1;
        cnt = 0;
      }
      _countShelfResult.add(TitleValuePair(
          title: row["shelf"],
          value: row["cnt"].toString(),
          color: colorList[colorIndex]));
      cnt++;
    }

    cnt = 0;
    for (final sqlite.Row row in sqlCountExtResultSet) {
      int colorIndex = cnt;
      if (colorIndex == colorList.length) {
        colorIndex = 0;
        cnt = 0;
      }
      _countExtResult.add(TitleValuePair(
          title: row["ext"],
          value: row["cnt"].toString(),
          color: colorList[colorIndex]));
      cnt++;
    }
    cnt = 0;
    for (final sqlite.Row row in sqlCountFileTypeResultSet) {
      int colorIndex = cnt;
      if (colorIndex == colorList.length) {
        colorIndex = 0;
        cnt = 0;
      }
      _countFileTypeResult.add(TitleValuePair(
          title: row["fileType"],
          value: row["cnt"].toString(),
          color: colorList[colorIndex]));
      cnt++;
    }
    if (refresh) {
      if (mounted) setState(() {});
    }
    if (db == null) actualDB.dispose();
  }

  String _getQueryCapsuleSQL() {
    String selectAllSQL = "SELECT c.* FROM capsule c";
    // if (_filterTags.isNotEmpty) {
    //   selectAllSQL = "$selectAllSQL $_capsuleLeftJoinCapsuleTag";
    // }
    return "$selectAllSQL ${_generateWhereClauseByKeyword()} ${_getOrderStatement()}";
  }

  String _getOrderStatement() {
    if (_orderFieldInfo != null) {
      return "ORDER BY c.${_orderFieldInfo!.orderFields ?? _orderFieldInfo!.name} ${_orderFieldInfo!.sort}";
    } else {
      return "";
    }
  }

  void _queryByTag(String tag) async {
    if (_filterTags.contains(tag) == false) {
      _filterTags.add(tag);
      _query();
    }
  }

  Future<void> _prepareForQuering() async {
    _currentKeyword = _keywordController.text;
    _listingFileProgressNotifier.value = _listingFileProgressNotifier.value
        .copyWith(finished: false, prompt: "正在查询");
    await Future.delayed(const Duration(milliseconds: 200));

    _fileInfos.clear();
    _clearAllSelection(false);
  }

  void _addTag() async {
    String tag = _newTagController.text;
    if (tag.isEmpty) {
      if (mounted) showSnackBar(context, "请输入标签");
      return;
    }
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      if (mounted) showSnackBar(context, "尚未建库");
      return;
    }

    final sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);

    String selectAllSQL = "SELECT * FROM tag WHERE tag='$tag'";
    sqlite.ResultSet resultSetTags;

    resultSetTags = db.select(selectAllSQL);
    if (resultSetTags.isEmpty) {
      String insertSQL = "INSERT INTO Tag(tag) VALUES('$tag')";
      db.execute(insertSQL);

      _tags.add(tag);
    }
    _addTagToCapsule(tag, db: db);
    _newTagController.value = TextEditingValue.empty;
    setState(() {});
    db.dispose();
  }

  void _addTagToCapsule(
    String tag, {
    sqlite.Database? db,
  }) {
    sqlite.Database actualDb = db ?? sqlite.sqlite3.open(_fileDBPath);
    List<FileInfo> fileInfos = [];

    bool? isFileList = _isFileList();
    if (isFileList != null) {
      if (isFileList == false) {
        for (MapEntry<int, FileInfo> entry in _selectionInQueryResult.entries) {
          fileInfos.add(entry.value);
        }
      } else {
        for (MapEntry<String, FileInfo> entry in _selectionInFileList.entries) {
          fileInfos.add(entry.value);
        }
      }
    }
    if (fileInfos.isNotEmpty) {
      String insertSQL = "INSERT INTO capsule_tag(capsule_name,tag) VALUES ";

      StringBuffer sbInsertSQL = StringBuffer(insertSQL);
      int cnt = 0;
      for (FileInfo fileInfo in fileInfos) {
        String capsuleTagSQL =
            "SELECT * FROM capsule_tag WHERE capsule_name='${fileInfo.title}' AND tag='$tag'";
        sqlite.ResultSet capsuleTagSet = actualDb.select(capsuleTagSQL);
        if (capsuleTagSet.isEmpty) {
          if (cnt > 0) {
            sbInsertSQL.write(",");
          }
          sbInsertSQL.write("('${fileInfo.title}','$tag')");
          if (_fileAndTagsMap.containsKey(fileInfo.title) == false) {
            _fileAndTagsMap[fileInfo.title] = [];
          }
          _fileAndTagsMap[fileInfo.title]!.add(tag);
          cnt++;
        }
      }
      if (cnt > 0) {
        actualDb.execute(sbInsertSQL.toString());
      }
    }
    if (db == null) {
      actualDb.dispose();
      setState(() {});
    }
  }

  void _deleteTagFromCapsule(
    String tag,
    FileInfo fileInfo, {
    sqlite.Database? db,
  }) {
    sqlite.Database actualDb = db ?? sqlite.sqlite3.open(_fileDBPath);

    String deleteCapsuleTagSQL =
        "DELETE FROM capsule_tag WHERE tag='$tag' AND capsule_name='${fileInfo.title}' ";

    actualDb.execute(deleteCapsuleTagSQL);

    if (_fileAndTagsMap.containsKey(fileInfo.title)) {
      _fileAndTagsMap[fileInfo.title]!.remove(tag);
    }
    if (db == null) {
      actualDb.dispose();
      setState(() {});
    }
  }

  void _updateQueryStatus() {
    _listingFileProgressNotifier.value = _listingFileProgressNotifier.value
        .copyWith(finished: true, prompt: "查到${_fileInfos.length}个结果");
  }

  void _updateFileListStatus() {
    _listingFileProgressNotifier.value = _listingFileProgressNotifier.value
        .copyWith(
            finished: true, prompt: "$_currentFolder：${_fileInfos.length}");
  }

  bool _isFileSelected(FileInfo fileInfo) {
    if (fileInfo.idInDB == null) {
      return _selectionInFileList.containsKey(fileInfo.fullPath);
    } else {
      return _selectionInQueryResult.containsKey(fileInfo.idInDB);
    }
  }

  void _showCumbersomeInputDlg() {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: InputDialog(
              prompt: "请输入要删除的内容（支持正则表达式）：",
              onOk: (value) async {
                Navigator.of(context).pop();
                _removeCumbersomeInFileNames(value);
              },
              onCancel: () {
                Navigator.of(context).pop();
              },
            ),
          );
        });
  }

  void _showReplaceInputDlg() {
    showDialog(
        context: context,
        builder: (context) {
          return Dialog(
            child: InputDialog(
              prompt: "请输入要替换的内容（支持正则表达式）：",
              prompt2: "替换为：",
              onReplace: (value, replacement) async {
                Navigator.of(context).pop();
                _replaceInFileNames(value, replacement);
              },
              onCancel: () {
                Navigator.of(context).pop();
              },
            ),
          );
        });
  }

  ///删除文件名中的那些累赘
  void _removeCumbersomeInFileNames(String cusbersome) async {
    _replaceInFileNames(cusbersome, "");
  }

  void _replaceInFileNames(String cusbersome, String replacement) async {
    File dbFile = File(_fileDBPath);
    bool exists = await dbFile.exists();
    final sqlite.Database? db =
        exists ? sqlite.sqlite3.open(_fileDBPath) : null;
    final updateCommand =
        db?.prepare("UPDATE capsule set name=?,path=? where name=?");
    final updateTagCommand = db
        ?.prepare("UPDATE capsule_tag set capsule_name=? where capsule_name=?");
    for (int i = 0; i < _fileInfos.length; i++) {
      final fileInfo = _fileInfos[i];
      String fullPath = fileInfo.fullPath;
      String name = fileInfo.name;
      String oldTitle = fileInfo.title;
      String newFullPath = "";
      String newName = name.replaceAll(cusbersome, replacement);
      List<String> newNameParts = newName.split(".");
      String ext = ".${newNameParts[newNameParts.length - 1]}";
      //如果替换完之后就只剩下扩展名了，那就不再替换
      if (newName.length > ext.length) {
        newFullPath = fullPath.replaceAll(cusbersome, replacement);
        File file = File(fullPath);
        if (await file.exists() == true) {
          await file.rename(newFullPath);
        }

        FileInfo newFileInfo = fileInfo.copyWith(fullPath: newFullPath);
        _fileInfos[i] = newFileInfo;
        if (_selectionInFileList.containsKey(fullPath)) {
          _selectionInFileList.remove(fullPath);
          _selectionInFileList[newFullPath] = newFileInfo;
        }

        if (newFileInfo.idInDB != null &&
            _selectionInQueryResult.containsKey(newFileInfo.idInDB) == true) {
          _selectionInQueryResult[newFileInfo.idInDB!] = newFileInfo;
        }

        updateCommand
            ?.execute([newFileInfo.name, newFileInfo.fullPath, oldTitle]);

        updateTagCommand?.execute([newFileInfo.title, oldTitle]);
        if (_fileAndTagsMap.containsKey(oldTitle)) {
          _fileAndTagsMap[newFileInfo.title] = _fileAndTagsMap[oldTitle]!;
          _fileAndTagsMap.remove(oldTitle);
        }
      }
    }
    updateCommand?.dispose();
    db?.dispose();

    setState(() {});
    if (mounted) {
      showSnackBar(context, "文件名中相同的部分替换完成！", messageType: MessageType.success);
    }
  }

  Future<void> _refreshData() async {
    sqlite.Database db = sqlite.sqlite3.open(_fileDBPath);
    await _getTagsFromDb(db: db, refresh: false);
    await _getCapsuleTagsFromDB(db: db, refresh: false);

    if (mounted) {
      setState(() {});
    }
    db.dispose();
  }

  void _replaceFileInfo(FileInfo oldFileInfo, FileInfo newFileInfo) {
    int index = _fileInfos.indexOf(oldFileInfo);
    if (index >= 0) {
      _fileInfos[index] = newFileInfo;
    }
  }

  void _moveFilesToFolder(bool isCoping) async {
    try {
      bool? isFileList = _isFileList();
      if (isFileList == null) return;
      if (_validateSelection() == false) return;
      String? folder =
          await FilePicker.platform.getDirectoryPath(dialogTitle: "请选择目标文件夹");

      bool updated = false;
      List<String> missedFiles = [];
      if (folder != null) {
        if (isFileList == true) {
          for (final entry in _selectionInFileList.entries) {
            FileInfo? newFileInfo =
                await _moveFileToFolder(entry.value, folder, isCoping);
            if (newFileInfo != null) {
              _selectionInFileList[entry.key] = newFileInfo;
              if (isCoping == false) {
                _replaceFileInfo(entry.value, newFileInfo);
                updated = true;
              }
            } else {
              missedFiles.add(entry.value.title);
            }
          }
        } else if (isFileList == false) {
          //如果是复制，就不用修改数据库中的文件夹地址
          if (isCoping) {
            for (final entry in _selectionInQueryResult.entries) {
              FileInfo? newFileInfo =
                  await _moveFileToFolder(entry.value, folder, isCoping);
              if (newFileInfo == null) {
                missedFiles.add(entry.value.title);
              }
            }
          } else {
            File dbFile = File(_fileDBPath);
            bool exists = await dbFile.exists();
            final sqlite.Database? db =
                exists ? sqlite.sqlite3.open(_fileDBPath) : null;
            final updateCommand =
                db?.prepare("UPDATE capsule SET name=?,path=? WHERE id=?");
            for (final entry in _selectionInQueryResult.entries) {
              FileInfo? newFileInfo =
                  await _moveFileToFolder(entry.value, folder, isCoping);
              if (newFileInfo != null) {
                _selectionInQueryResult[entry.key] = newFileInfo;
                updateCommand?.execute([
                  newFileInfo.name,
                  newFileInfo.fullPath,
                  newFileInfo.idInDB
                ]);

                _replaceFileInfo(entry.value, newFileInfo);
                updated = true;
              } else {
                missedFiles.add(entry.value.title);
              }
            }
            updateCommand?.dispose();
            db?.dispose();
          }
        }
        if (mounted) {
          showSnackBar(context,
              "${isCoping ? "复制" : "拷贝"}完毕！${missedFiles.isEmpty ? "" : '${missedFiles.join(",")}等不存在！'}");
          if (updated) {
            setState(() {});
          }
        }
      }
    } catch (ex) {
      if (mounted) {
        showSnackBar(context, "${isCoping ? "复制" : "拷贝"}出现错误：${ex.toString()}");
      }
    }
  }

  Future<FileInfo?> _moveFileToFolder(
      FileInfo fileInfo, String folder, bool isCoping) async {
    File file = File(fileInfo.fullPath);
    String newPath = "$folder\\${fileInfo.name}";
    if (newPath.toLowerCase() != fileInfo.fullPath.toLowerCase()) {
      if (await file.exists() == true) {
        file.copy(newPath);
        if (isCoping == false) {
          file.delete();
        }
      }
      FileInfo newFileInfo = fileInfo.copyWith(fullPath: newPath);

      _fileInfos[_fileInfos.indexOf(fileInfo)] = newFileInfo;
      return newFileInfo;
    }
    return null;
  }

  Future<void> _getTagsFromDb(
      {sqlite.Database? db, bool refresh = true}) async {
    File dbFile = File(_fileDBPath);
    bool existed = await dbFile.exists();
    if (existed == false) {
      return;
    }

    _tags.clear();

    final sqlite.Database actualDB = db ?? sqlite.sqlite3.open(_fileDBPath);

    const String selectAllTagSQL = "SELECT * FROM tag";
    sqlite.ResultSet resultSet;

    resultSet = actualDB.select(selectAllTagSQL);

    for (final sqlite.Row row in resultSet) {
      _tags.add(row["tag"]);
    }
    if (refresh) {
      if (mounted) setState(() {});
    }
    if (db == null) actualDB.dispose();
  }

  void _orderFileInfos() {
    if (_orderFieldInfo == null) return;
    if (_orderFieldInfo!.name == "nameASC") {
      _fileInfos.sort((a, b) => a.shortPinyin!.compareTo(b.shortPinyin!));
    } else if (_orderFieldInfo!.name == "nameDESC") {
      _fileInfos.sort((a, b) => b.shortPinyin!.compareTo(a.shortPinyin!));
    } else if (_orderFieldInfo!.name == "lastModifiedASC") {
      _fileInfos.sort((a, b) {
        if (a.lastModified == null && b.lastModified == null) {
          return a.shortPinyin!.compareTo(b.shortPinyin!);
        }
        if (a.lastModified == null && b.lastModified != null) return -1;
        if (a.lastModified != null && b.lastModified == null) return 1;
        return a.lastModified! - b.lastModified!;
      });
    } else if (_orderFieldInfo!.name == "lastModifiedDESC") {
      _fileInfos.sort((a, b) {
        if (a.lastModified == null && b.lastModified == null) {
          return b.shortPinyin!.compareTo(a.shortPinyin!);
        }
        if (a.lastModified == null && b.lastModified != null) return 1;
        if (a.lastModified != null && b.lastModified == null) return -1;
        return b.lastModified! - a.lastModified!;
      });
    }

    setState(() {});
  }

  void _mergeDB() async {
    FilePickerResult? dbFile =
        await FilePicker.platform.pickFiles(allowedExtensions: ["db"]);
    if (dbFile != null && dbFile.files.isNotEmpty) {
      sqlite.Database? sourceDB;
      sqlite.Database? targetDB;
      try {
        sourceDB = sqlite.sqlite3.open(dbFile.files[0].path!);
        String selectAllCapsuleSQL = "SELECT * FROM capsule";
        sqlite.ResultSet sourceCapsuleSet =
            sourceDB.select(selectAllCapsuleSQL);
        if (sourceCapsuleSet.isNotEmpty) {
          bool isCapsuleChanged = false;
          bool isTagChanged = false;
          bool isCapsuleTagChanged = false;
          //合并capsule，去重
          targetDB = sqlite.sqlite3.open(_fileDBPath);
          sqlite.ResultSet targetCapsuleSet =
              targetDB.select(selectAllCapsuleSQL);
          final Map<String, sqlite.Row> existedCapsuleMap = {};
          for (final sqlite.Row row in targetCapsuleSet) {
            existedCapsuleMap[row["name"]] = row;
          }

          String insertIntoCapsuleSQL =
              "INSERT INTO capsule(name,path,shelf,size,sizeString,ext,category,fileType,lastAccessed,lastModified,importTime,shortPinyin,summary) VALUES";
          int counter = 0;
          StringBuffer sbInsertIntoCapsuleSQL =
              StringBuffer(insertIntoCapsuleSQL);

          for (final sqlite.Row row in sourceCapsuleSet) {
            if (existedCapsuleMap.containsKey(row["name"])) continue;
            if (counter > 0) {
              sbInsertIntoCapsuleSQL.write(",");
            }
            sbInsertIntoCapsuleSQL.write(
                "('${row["name"]}','${row["path"]}','${row["shelf"]}',"
                "${row["size"]},'${row["sizeString"]}','${row["ext"]}',"
                "'${row["category"]}','${row["fileType"]}',"
                "${row["lastAccessed"]},${row["lastModified"]},"
                "${row["importTime"]},'${row["shortPinyin"]}','${row["summary"]}')");
            counter++;
          }
          if (counter > 0) {
            isCapsuleChanged = true;
            targetDB.execute(sbInsertIntoCapsuleSQL.toString());
          }

          //合并标签tag,去重
          String selectAllTagSQL = "SELECT * FROM tag";
          sqlite.ResultSet targetTagsSet = targetDB.select(selectAllTagSQL);
          Map<String, bool> tagExistedMap = {};
          for (final row in targetTagsSet) {
            tagExistedMap[row["tag"]] = true;
          }
          counter = 0;
          sqlite.ResultSet sourceTagsSet = sourceDB.select(selectAllTagSQL);
          String insertIntoTagSQL = "INSERT INTO tag(tag) VALUES";
          StringBuffer sbInsertIntoTagSQL = StringBuffer(insertIntoTagSQL);
          for (final sqlite.Row row in sourceTagsSet) {
            if (tagExistedMap.containsKey(row["tag"])) continue;
            if (counter > 0) {
              sbInsertIntoTagSQL.write(",");
            }
            sbInsertIntoTagSQL.write("('${row["tag"]}')");
            counter++;
          }
          if (counter > 0) {
            isTagChanged = true;
            targetDB.execute(sbInsertIntoTagSQL.toString());
          }

          //合并capsule_tag，去重
          String selectAllCapsuleTagSQL = "SELECT * FROM capsule_tag";

          sqlite.ResultSet targetCapsuleTagSet =
              targetDB.select(selectAllCapsuleTagSQL);
          Map<String, bool> capsuleTagExistedMap = {};
          for (final row in targetCapsuleTagSet) {
            capsuleTagExistedMap["${row["capsule_name"]}_${row["tag"]}}"] =
                true;
          }
          counter = 0;
          sqlite.ResultSet sourceCapsuleTagSet =
              sourceDB.select(selectAllCapsuleTagSQL);
          String insertIntoCapsuleTagSQL =
              "INSERT INTO capsule_tag(capsule_name,tag) VALUES";
          StringBuffer sbInsertIntoCapsuleTagSQL =
              StringBuffer(insertIntoCapsuleTagSQL);
          for (final sqlite.Row row in sourceCapsuleTagSet) {
            if (capsuleTagExistedMap
                .containsKey("${row["capsule_name"]}_${row["tag"]}}")) continue;
            if (counter > 0) {
              sbInsertIntoCapsuleTagSQL.write(",");
            }
            sbInsertIntoCapsuleTagSQL
                .write("('${row["capsule_name"]}','${row["tag"]}')");
            counter++;
          }
          if (counter > 0) {
            isCapsuleTagChanged = true;
            targetDB.execute(sbInsertIntoCapsuleTagSQL.toString());
          }

          if (mounted) {
            showSnackBar(context, "合并成功", messageType: MessageType.success);
            if (isTagChanged) {
              await _getTagsFromDb(db: targetDB, refresh: false);
            }
            if (isCapsuleTagChanged) {
              await _getCapsuleTagsFromDB(db: targetDB, refresh: false);
            }

            if (isCapsuleChanged) {
              await _query(db: targetDB, refresh: false);
            }
            if (mounted) setState(() {});
          }
        } else {
          if (mounted) {
            showSnackBar(context, "您选择的数据库为空，没什么好合并的！",
                messageType: MessageType.warning);
          }
        }
      } catch (ex) {
        if (mounted) {
          showSnackBar(context, "打开数据库错误！${ex.toString()}",
              messageType: MessageType.error, durationSeconds: 30);
        }
      } finally {
        if (targetDB != null) targetDB.dispose();
        if (sourceDB != null) sourceDB.dispose();
      }
    }
  }

  Widget _buildToolbar() {
    double barPanelHeight = 40;
    BoxDecoration barPanelDecoration = BoxDecoration(
        borderRadius: BorderRadius.circular(5), color: Colors.white);
    const SizedBox barPanelSpacer = SizedBox(
      width: 5,
    );
    return Container(
      padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
      color: const Color(0xFFEAEAEA),
      child: Row(
        children: [
          Container(
            height: barPanelHeight,
            padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
            decoration: barPanelDecoration,
            child: Row(children: [
              // FilterGroup(
              //   isVertical: false,
              //   currentFilter: _currentFilter,
              //   onChanged: (value) {
              //     _currentFilter = value!;
              //   },
              // ),
              _ConfigFileTypeSelectionButton(
                menuSelected: (value) {
                  _filterFileTypeMap = value;
                },
              ),
              const SizedBox(
                width: 5,
              ),
              IconButton(
                onPressed: _listFiles,
                icon: const Icon(Icons.folder),
                tooltip: "选择文件夹",
              ),
              const SizedBox(
                width: 5,
              ),
              IconButton(
                  onPressed: _refreshData,
                  tooltip: "刷新(仅标签相关)",
                  icon: const Icon(Icons.refresh_rounded)),
            ]),
          ),
          barPanelSpacer,
          Container(
            height: barPanelHeight,
            padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
            decoration: barPanelDecoration,
            child: Row(children: [
              IconButton(
                  tooltip: "将当前文件列表入库",
                  onPressed: _isFileList() == true && _fileInfos.isNotEmpty
                      ? _showAddFileToDBDlg
                      : null,
                  icon: const Icon(Icons.dataset_linked_rounded)),
              IconButton(
                  tooltip: "从库中清除当前所有记录",
                  color: dangerColor,
                  onPressed: _isFileList() == false && _fileInfos.isNotEmpty
                      ? _clearCurrentDataset
                      : null,
                  icon: const Icon(
                    Icons.playlist_remove,
                  )),
              IconButton(
                  tooltip: "从库中清除选中的记录",
                  color: dangerColor,
                  onPressed: _isFileList() == false && _fileInfos.isNotEmpty
                      ? _clearSelectedRecords
                      : null,
                  icon: const Icon(
                    Icons.rule,
                  )),
              IconButton(
                  tooltip: "删除选中的记录及其对应的文件",
                  color: dangerColor,
                  onPressed: !_isSelectionEmpty() ? _deleteSelection : null,
                  icon: const Icon(
                    Icons.delete_forever_rounded,
                  )),
              IconButton(
                  tooltip: "删除列表中选中的数据",
                  onPressed: _selectionInFileList.isNotEmpty ||
                          _selectionInQueryResult.isNotEmpty
                      ? _onlyRemoveSelectedItems
                      : null,
                  icon: const Icon(Icons.remove_from_queue_rounded)),
              IconButton(
                  tooltip: "仅清除列表中的数据",
                  onPressed: _clearItemInList,
                  icon: const Icon(Icons.switch_access_shortcut_rounded)),
              IconButton(
                tooltip: "清除选择",
                onPressed: _selectionInFileList.isNotEmpty ||
                        _selectionInQueryResult.isNotEmpty
                    ? () {
                        _clearAllSelection(true);
                      }
                    : null,
                icon: const Icon(Icons.select_all_rounded),
              ),
              IconButton(
                  tooltip: "选择所有",
                  onPressed: _fileInfos.isNotEmpty ? _selectAll : null,
                  icon: const Icon(
                    Icons.task_alt_rounded,
                  )),
              IconButton(
                tooltip: "清除文件名中的相同部分",
                onPressed: _fileInfos.isEmpty ? null : _showCumbersomeInputDlg,
                icon: const Icon(Icons.drive_file_rename_outline_rounded),
              ),
              IconButton(
                tooltip: "替换文件名中的相同部分",
                onPressed: _fileInfos.isEmpty ? null : _showReplaceInputDlg,
                icon: const Icon(Icons.find_replace_rounded),
              ),
              IconButton(
                tooltip: "将选中文件移动到指定目录",
                onPressed: () {
                  _moveFilesToFolder(false);
                },
                icon: const Icon(Icons.drive_file_move_rounded),
              ),
              IconButton(
                tooltip: "将选中文件复制到指定目录",
                onPressed: () {
                  _moveFilesToFolder(true);
                },
                icon: const Icon(Icons.file_copy_rounded),
              ),
              IconButton(
                tooltip: "去除库中重复的记录",
                onPressed: () {
                  _removeRepeatedCapsules();
                },
                icon: const Icon(Icons.delete_sweep_rounded),
              ),
              IconButton(
                tooltip: "生成名称简拼",
                onPressed: () {
                  _setShortPinyinInDB();
                },
                icon: const Icon(Icons.abc_rounded),
              ),
              IconButton(
                tooltip: "合并库",
                onPressed: () {
                  _mergeDB();
                },
                icon: const Icon(Icons.join_full_rounded),
              ),
              if (_isAddingToDb) ...[
                const SizedBox(
                  width: 5,
                ),
                SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(
                    value: _addingToDBProgressValue,
                    backgroundColor: Colors.white,
                    strokeWidth: 1.5,
                  ),
                ),
                const SizedBox(
                  width: 10,
                ),
                Text(_addingProgressLabel),
                IconButton(
                    onPressed: () {
                      _cancelAddingToDB = true;
                    },
                    icon: const Icon(Icons.cancel))
              ]
            ]),
          ),
          barPanelSpacer,
          Container(
              height: barPanelHeight,
              padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
              decoration: barPanelDecoration,
              child: Row(
                children: [
                  _OrderByButton(
                      defaultOrderByField: _orderFieldInfo,
                      menuClick: (value) {
                        _orderFieldInfo = value;
                        _orderFileInfos();
                      },
                      orderByFields: _orderFields)
                ],
              ))
        ],
      ),
    );
  }

  void _deleteFilterTag(String tag) {
    _filterTags.remove(tag);
    _query();
  }

  Widget _buildTitle(FileInfo fileInfo) {
    return TagBoard(
      tag: fileInfo.title,
      isBlock: false,
      onDelete: (s) {
        _deleteFileInfo(fileInfo);
      },
      onEdit: (s) {
        _showFileInfoEditDlg(fileInfo);
      },
    );
  }

  Widget _buildFullpath(FileInfo fileInfo) {
    BoxDecoration sizeStringBoxDecoration = BoxDecoration(
        borderRadius: BorderRadius.circular(5), color: const Color(0xFFEFDEEF));
    if (fileInfo.shelf == null || fileInfo.shelf!.isEmpty) {
      return Text(fileInfo.fullPath);
    } else {
      return Text.rich(TextSpan(children: [
        WidgetSpan(
            child: Container(
          padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
          decoration: sizeStringBoxDecoration,
          child: Text(fileInfo.shelf!, style: const TextStyle(fontSize: 12)),
        )),
        const WidgetSpan(
            child: SizedBox(
          width: 5,
        )),
        TextSpan(text: fileInfo.fullPath)
      ]));
    }
  }

  @override
  Widget build(BuildContext context) {
    TextStyle defaultListTileTitleStyle =
        AppThemeData.globalTextTheme.bodyMedium!;
    double listTileTitleSize =
        (AppThemeData.globalTextTheme.bodyMedium?.fontSize ?? 14) + 2;
    ThemeData theme = Theme.of(context);
    TextStyle titleStyle = TextStyle(
        fontSize: listTileTitleSize,
        fontWeight: defaultListTileTitleStyle.fontWeight,
        color: theme.colorScheme.onSurface);
    BoxDecoration sizeStringBoxDecoration = BoxDecoration(
        borderRadius: BorderRadius.circular(5), color: const Color(0xFFDEEFEF));
    Size screenSize = MediaQuery.of(context).size;
    double toolbarHeight = 55;
    double infoPanelHeight = (screenSize.height - toolbarHeight - 80);
    return Scaffold(
        body: Column(
      children: [
        _buildToolbar(),
        Expanded(
            child: SplitView(
          controller: _splitViewController,
          gripColor: const Color(0xFFAAAAAA),
          gripSize: 7,
          indicator: const SplitIndicator(
            viewMode: SplitViewMode.Horizontal,
            color: Colors.white,
          ),
          viewMode: SplitViewMode.Horizontal,
          children: [
            Container(
              color: const Color(0xFFEAEAEA),
              padding: const EdgeInsets.all(5),
              height: screenSize.height - toolbarHeight,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  Container(
                    padding: const EdgeInsets.fromLTRB(5, 3, 5, 3),
                    child: ProgressPromptBar(
                        notifier: _listingFileProgressNotifier),
                  ),
                  FormInputWithIcon(
                    onSubmitted: (s) => _query(),
                    controller: _keywordController,
                    hintText: '在库中查询',
                    icon: const Icon(Icons.search_rounded),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                  SizedBox(
                    height: infoPanelHeight < 0 ? 100 : infoPanelHeight,
                    child: SingleChildScrollView(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          _InfoPanel(
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                _TagsPanel(
                                  tags: _tags,
                                  tagClick: _queryByTag,
                                  onDeleteTag: _deleteTag,
                                  onEditTag: _editTag,
                                  onSelectTag: _addTagToCapsule,
                                ),
                                const SizedBox(
                                  height: 10,
                                ),
                                FormInputWithIcon(
                                  onSubmitted: (s) => _addTag(),
                                  controller: _newTagController,
                                  hintText: '添加或设置标签',
                                  icon: const Icon(Icons.bookmark_add),
                                ),
                              ],
                            ),
                          ),
                          if (_filterTags.isNotEmpty) ...[
                            const SizedBox(
                              height: 10,
                            ),
                            _InfoPanel(Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Row(
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                                  children: [
                                    const Text("标签过滤"),
                                    InkWell(
                                      onTap: () {
                                        _filterTags.clear();
                                        _query();
                                      },
                                      child: const Icon(
                                        Icons.delete,
                                        size: 18,
                                      ),
                                    )
                                  ],
                                ),
                                const Divider(
                                  height: 20,
                                ),
                                _TagsPanel(
                                  tags: _filterTags,
                                  alwaysShowButton: true,
                                  onDeleteTag: _deleteFilterTag,
                                ),
                              ],
                            )),
                          ],
                          const SizedBox(
                            height: 10,
                          ),
                          _InfoPanel(CountPanel(
                            countPairs: _countShelfResult,
                          )),
                          const SizedBox(
                            height: 10,
                          ),
                          _InfoPanel(CountPanel(
                            countPairs: _countExtResult,
                          )),
                          const SizedBox(
                            height: 10,
                          ),
                          _InfoPanel(CountPanel(
                            countPairs: _countFileTypeResult,
                          )),
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
            SelectionArea(
                child: SizedBox(
              height: screenSize.height - 60,
              child: ListView.builder(
                  itemCount: _fileInfos.length,
                  itemBuilder: (context, index) {
                    FileInfo fileInfo = _fileInfos[index];
                    List<String>? tags = _fileAndTagsMap[fileInfo.title];
                    return ListTile(
                      leading: InkWell(
                        onTap: () {
                          if (fileInfo.idInDB == null) {
                            final fullPath = fileInfo.fullPath;
                            if (_selectionInFileList.containsKey(fullPath)) {
                              _selectionInFileList.remove(fullPath);
                            } else {
                              _selectionInFileList[fullPath] = fileInfo;
                            }
                          } else {
                            final int id = fileInfo.idInDB!;
                            if (_selectionInQueryResult.containsKey(id)) {
                              _selectionInQueryResult.remove(id);
                            } else {
                              _selectionInQueryResult[id] = fileInfo;
                            }
                          }
                          setState(() {});
                        },
                        child: Container(
                          alignment: Alignment.center,
                          width: 36,
                          height: 36,
                          decoration: BoxDecoration(
                              color: _isFileSelected(fileInfo)
                                  ? Colors.red
                                  : Colors.green,
                              borderRadius: BorderRadius.circular(18)),
                          child: Text(
                            fileInfo.ext!,
                            style: const TextStyle(color: Colors.white),
                          ),
                        ),
                      ),
                      title: fileInfo.sizeString != null
                          ? Text.rich(TextSpan(children: [
                              WidgetSpan(child: _buildTitle(fileInfo)),
                              const WidgetSpan(
                                  child: SizedBox(
                                width: 5,
                              )),
                              WidgetSpan(
                                  child: Container(
                                padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
                                decoration: sizeStringBoxDecoration,
                                child: Text(fileInfo.sizeString!,
                                    style: titleStyle.copyWith(fontSize: 12)),
                              ))
                            ]))
                          : _buildTitle(fileInfo),
                      subtitle: (tags != null && tags.isNotEmpty) ||
                              (fileInfo.summary != null &&
                                  fileInfo.summary!.isNotEmpty)
                          ? Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              mainAxisAlignment: MainAxisAlignment.start,
                              children: [
                                _buildFullpath(fileInfo),
                                const SizedBox(
                                  height: 5,
                                ),
                                if (fileInfo.summary != null)
                                  Text(fileInfo.summary!),
                                if (tags != null)
                                  Wrap(
                                    spacing: 5,
                                    crossAxisAlignment:
                                        WrapCrossAlignment.start,
                                    children: [
                                      for (final String tag in tags)
                                        TagBoard(
                                          tag: tag,
                                          onDelete: (s) {
                                            _deleteTagFromCapsule(s, fileInfo);
                                          },
                                        )
                                    ],
                                  )
                              ],
                            )
                          : _buildFullpath(fileInfo),
                      onTap: () {
                        if (fileInfo.idInDB == null) {
                          _selectionInFileList.clear();
                          final fullPath = fileInfo.fullPath;

                          _selectionInFileList[fullPath] = fileInfo;
                        } else {
                          _selectionInQueryResult.clear();
                          final int id = fileInfo.idInDB!;
                          _selectionInQueryResult[id] = fileInfo;
                        }
                        setState(() {});
                      },
                      onLongPress: () {
                        OpenAppFile.open(fileInfo.fullPath);
                      },
                    );
                  }),
            ))
          ],
        ))
      ],
    )

        // This trailing comma makes auto-formatting nicer for build methods.
        );
  }
}

class _InfoPanel extends StatelessWidget {
  final Widget child;
  const _InfoPanel(this.child);
  @override
  Widget build(BuildContext context) {
    return Container(
        padding: const EdgeInsets.all(10),
        width: double.infinity,
        decoration: BoxDecoration(
            color: const Color(0xFFAAAAAA),
            borderRadius: BorderRadius.circular(3)),
        child: child);
  }
}

class _TagsPanel extends StatefulWidget {
  final List<String> tags;
  final bool alwaysShowButton;
  final VoidCallbackOneString? tagClick;
  final VoidCallbackOneString? onDeleteTag;
  final VoidCallbackOneString? onEditTag;
  final VoidCallbackOneString? onSelectTag;
  const _TagsPanel(
      {required this.tags,
      this.tagClick,
      this.onDeleteTag,
      this.onEditTag,
      this.onSelectTag,
      this.alwaysShowButton = false});

  @override
  State<StatefulWidget> createState() => _TagsPanelState();
}

class _TagsPanelState extends State<_TagsPanel> {
  String? _hoveredTag;
  @override
  Widget build(BuildContext context) {
    return Wrap(
      crossAxisAlignment: WrapCrossAlignment.start,
      runSpacing: 5,
      spacing: 5,
      children: [
        for (final String tag in widget.tags)
          TagBoard(
            tag: tag,
            onHover: (s) {
              setState(() {
                _hoveredTag = s;
              });
            },
            showButtons: _hoveredTag == tag,
            alwaysShowButton: widget.alwaysShowButton,
            tagClick: widget.tagClick,
            onDelete: widget.onDeleteTag,
            onEdit: widget.onEditTag,
            onSelect: widget.onSelectTag,
          )
      ],
    );
  }
}

class _ConfigFileTypeSelectionButton extends StatefulWidget {
  const _ConfigFileTypeSelectionButton({this.menuSelected, this.menuClick});

  final PopupMenuItemSelected<ConfigFileType>? menuClick;
  final PopupMenuItemSelected<Map<String, bool>>? menuSelected;
  @override
  State<StatefulWidget> createState() => _ConfigFileTypeSelectionButtonState();
}

class _ConfigFileTypeSelectionButtonState
    extends State<_ConfigFileTypeSelectionButton> {
  final Map<String, bool> _selectedMap = {};
  bool _isLoaded = false;
  @override
  void initState() {
    super.initState();
  }

  void _init() {
    if (appConfig.fileTypes != null) {
      for (final fileType in appConfig.fileTypes!) {
        _selectedMap[fileType.name] = false;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoaded == false) {
      _init();
      _isLoaded = true;
    }
    return Row(
      children: [
        PopupMenuButton<ConfigFileType>(
          padding: const EdgeInsets.all(0),
          menuPadding: const EdgeInsets.all(8),
          onSelected: (value) {
            setState(() {
              _selectedMap[value.name] = !_selectedMap[value.name]!;
            });
            widget.menuSelected?.call(_selectedMap);
          },
          position: PopupMenuPosition.under,
          tooltip: "选择文件类型",
          itemBuilder: (context) {
            return appConfig.fileTypes!
                .map<PopupMenuEntry<ConfigFileType>>((e) {
              return CheckedPopupMenuItem(
                padding: const EdgeInsets.all(1),
                value: e,
                height: 18,
                checked: _selectedMap[e.name]!,
                onTap: () {
                  widget.menuClick?.call(e);
                },
                child: Text(e.name),
              );
            }).toList();
          },
          child: const Row(
            children: [
              Icon(Icons.filter_vintage_rounded),
              Icon(Icons.arrow_drop_down)
            ],
          ),
        )
      ],
    );
  }
}

class _OrderByButton extends StatefulWidget {
  const _OrderByButton(
      {this.menuClick, required this.orderByFields, this.defaultOrderByField});
  final List<OrderFieldInfo> orderByFields;
  final OrderFieldInfo? defaultOrderByField;
  final PopupMenuItemSelected<OrderFieldInfo>? menuClick;
  @override
  State<StatefulWidget> createState() => _OrderByButtonState();
}

class _OrderByButtonState extends State<_OrderByButton> {
  OrderFieldInfo? _selectedField;

  @override
  void initState() {
    super.initState();
    _selectedField = widget.defaultOrderByField ?? widget.orderByFields[0];
  }

  @override
  Widget build(BuildContext context) {
    Icon dropdownIcon = const Icon(Icons.low_priority_sharp);
    return Row(
      children: [
        PopupMenuButton<OrderFieldInfo>(
          padding: const EdgeInsets.all(0),
          menuPadding: const EdgeInsets.all(8),
          onSelected: (value) {
            setState(() {
              _selectedField = value;
            });
          },
          position: PopupMenuPosition.under,
          tooltip: "排序",
          itemBuilder: (context) {
            return widget.orderByFields
                .map<PopupMenuEntry<OrderFieldInfo>>((e) {
              return CheckedPopupMenuItem(
                padding: const EdgeInsets.all(1),
                value: e,
                height: 18,
                checked: _selectedField?.name == e.name,
                onTap: () {
                  widget.menuClick?.call(e);
                },
                child: Text(e.title ?? e.name),
              );
            }).toList();
          },
          child: Row(
            children: [
              if (_selectedField != null) ...[
                dropdownIcon,
                Text(_selectedField!.title ?? _selectedField!.name)
              ] else
                dropdownIcon,
              const Icon(Icons.arrow_drop_down)
            ],
          ),
        )
      ],
    );
  }
}
